package top.lyunk.demo;

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <pre>
 * 说明：
 * </pre>
 *
 * @author lyk
 * @date 2022/9/9
 */
public class App {
    private static List<NeedTodo> list = new ArrayList<>();
    private static byte[] compilerByte;
    static String className = "top.lyunk.demo.bytebuddy.HelloPrint";
    // static MyClassLoader classLoader = new MyClassLoader();

    public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        compiler();

        domain();
        System.out.println("打印1:" + getaClass());
        System.out.println("打印1");
        System.gc();
        System.out.println("打印2:" + getaClass());
        System.out.println("打印2");
        domain();
        System.out.println("打印2:" + getaClass());
        System.out.println("打印2");
    }

    private static Class<?> getaClass() {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void compiler() throws IOException {

        //language=JAVA
        String content = "package top.lyunk.demo.bytebuddy;\n" +
                "\n" +
                "public class HelloPrint implements top.lyunk.demo.App.NeedTodo {\n" +
                "    @Override\n" +
                "    public void print(String str) " +
                "{\n" +
                "        System.out.println(\"Hello:\" + str);\n" +
                "    }\n" +
                "}";
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
             MyJavaObjectManager fm = new MyJavaObjectManager(fileManager)) {
            compiler.getTask(null, fm, null, null, null, Collections.singleton(new JavaSourceFromString(className, content)))
                    .call();
            compilerByte = fm.getCompiledBytes();
        }
    }

    public static void domain() throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> aClass = classLoader.defineClass(className, compilerByte);
        System.out.println("================domain===============");
        System.out.println(aClass);
        System.out.println("domain:" + getaClass(classLoader));
        System.out.println(aClass.newInstance());
        System.out.println("domain:" + getaClass(classLoader));
        System.out.println("================domain===============");
    }

    private static  Class<?>  getaClass(MyClassLoader classLoader) {
        try {
            return Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static class JavaSourceFromString extends SimpleJavaFileObject {
        String code;

        private ByteArrayOutputStream outputStream;

        public JavaSourceFromString(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension),
                    Kind.SOURCE);
            this.code = code;
            this.outputStream = new ByteArrayOutputStream();
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return code;
        }
    }

    public static class ClassFileObject extends SimpleJavaFileObject {
        private ByteArrayOutputStream byteArrayOutputStream; //字节数组输出流

        public ClassFileObject(String name, Kind kind) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension),
                    kind);
            this.byteArrayOutputStream = new ByteArrayOutputStream();
        }

        //编译完成后会回调OutputStream，回调成功后，我们就可以通过下面的getByteCode()方法获取编译后的字节码字节数组
        @Override
        public OutputStream openOutputStream() throws IOException {
            return byteArrayOutputStream;
        }
        //将输出流中的字节码转换为字节数组
        public byte[] getCompiledBytes() {
            return byteArrayOutputStream.toByteArray();
        }
    }

    public static class MyJavaObjectManager extends ForwardingJavaFileManager<JavaFileManager>{
        private ClassFileObject classObject; //我们自定义的JavaFileObject

        protected MyJavaObjectManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        //重写该方法，使其返回我们的ClassJavaFileObject
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
                                                   FileObject sibling) throws IOException {
            classObject= new ClassFileObject(className, kind);
            return classObject;
        }
        //将输出流中的字节码转换为字节数组
        public byte[] getCompiledBytes() {
            return classObject.getCompiledBytes();
        }


    }

    public interface NeedTodo {
        void print(String str);
    }

    public static class MyClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b){
            return super.defineClass(name, b, 0, b.length, null);
        }
    }


}
